Passed
Branch v9.1.x (22fd25)
by Rafael S.
02:40
created

WaveFileParser.getBextBytes_   A

Complexity

Conditions 2

Size

Total Lines 30
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 27
dl 0
loc 30
rs 9.232
c 0
b 0
f 0
cc 2
1
/*
2
 * Copyright (c) 2017-2019 Rafael da Silva Rocha.
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining
5
 * a copy of this software and associated documentation files (the
6
 * "Software"), to deal in the Software without restriction, including
7
 * without limitation the rights to use, copy, modify, merge, publish,
8
 * distribute, sublicense, and/or sell copies of the Software, and to
9
 * permit persons to whom the Software is furnished to do so, subject to
10
 * the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be
13
 * included in all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
 *
23
 */
24
25
/**
26
 * @fileoverview The WaveFileParser class.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
import { WaveFileReader } from './wavefile-reader';
31
import writeString from './write-string';
32
import { packTo, packStringTo, packString, pack } from 'byte-data';
33
34
/**
35
 * A class to read and write wav files.
36
 * @extends WaveFileReader
37
 */
38
export class WaveFileParser extends WaveFileReader {
39
40
  /**
41
   * Return a byte buffer representig the WaveFileParser object as a .wav file.
42
   * The return value of this method can be written straight to disk.
43
   * @return {!Uint8Array} A wav file.
44
   */
45
  toBuffer() {
46
    this.uInt16.be = this.container === 'RIFX';
47
    this.uInt32.be = this.uInt16.be;
48
    /** @type {!Array<!Array<number>>} */
49
    let fileBody = [
50
      this.getJunkBytes_(),
51
      this.getDs64Bytes_(),
52
      this.getBextBytes_(),
53
      this.getiXMLBytes_(),
54
      this.getFmtBytes_(),
55
      this.getFactBytes_(),
56
      packString(this.data.chunkId),
57
      pack(this.data.samples.length, this.uInt32),
58
      this.data.samples,
59
      this.getCueBytes_(),
60
      this.getSmplBytes_(),
61
      this.getLISTBytes_(),
62
      this.get_PMXBytes_()
63
    ];
64
    /** @type {number} */
65
    let fileBodyLength = 0;
66
    for (let i=0; i<fileBody.length; i++) {
67
      fileBodyLength += fileBody[i].length;
68
    }
69
    /** @type {!Uint8Array} */
70
    let file = new Uint8Array(fileBodyLength + 12);
71
    /** @type {number} */
72
    let index = 0;
73
    index = packStringTo(this.container, file, index);
74
    index = packTo(fileBodyLength + 4, this.uInt32, file, index);
75
    index = packStringTo(this.format, file, index);
76
    for (let i=0; i<fileBody.length; i++) {
77
      file.set(fileBody[i], index);
78
      index += fileBody[i].length;
79
    }
80
    return file;
81
  }
82
83
  /**
84
   * Return the bytes of the 'bext' chunk.
85
   * @private
86
   */
87
  getBextBytes_() {
88
    /** @type {!Array<number>} */
89
    let bytes = [];
90
    this.enforceBext_();
91
    if (this.bext.chunkId) {
92
      this.bext.chunkSize = 602 + this.bext.codingHistory.length;
93
      bytes = bytes.concat(
94
        packString(this.bext.chunkId),
95
        pack(602 + this.bext.codingHistory.length, this.uInt32),
96
        writeString(this.bext.description, 256),
97
        writeString(this.bext.originator, 32),
98
        writeString(this.bext.originatorReference, 32),
99
        writeString(this.bext.originationDate, 10),
100
        writeString(this.bext.originationTime, 8),
101
        pack(this.bext.timeReference[0], this.uInt32),
102
        pack(this.bext.timeReference[1], this.uInt32),
103
        pack(this.bext.version, this.uInt16),
104
        writeString(this.bext.UMID, 64),
105
        pack(this.bext.loudnessValue, this.uInt16),
106
        pack(this.bext.loudnessRange, this.uInt16),
107
        pack(this.bext.maxTruePeakLevel, this.uInt16),
108
        pack(this.bext.maxMomentaryLoudness, this.uInt16),
109
        pack(this.bext.maxShortTermLoudness, this.uInt16),
110
        writeString(this.bext.reserved, 180),
111
        writeString(
112
          this.bext.codingHistory, this.bext.codingHistory.length));
113
    }
114
    this.enforceByteLen_(bytes);
115
    return bytes;
116
  }
117
118
  /**
119
   * Make sure a 'bext' chunk is created if BWF data was created in a file.
120
   * @private
121
   */
122
  enforceBext_() {
123
    for (let prop in this.bext) {
124
      if (this.bext.hasOwnProperty(prop)) {
125
        if (this.bext[prop] && prop != 'timeReference') {
126
          this.bext.chunkId = 'bext';
127
          break;
128
        }
129
      }
130
    }
131
    if (this.bext.timeReference[0] || this.bext.timeReference[1]) {
132
      this.bext.chunkId = 'bext';
133
    }
134
  }
135
136
  /**
137
   * Return the bytes of the 'iXML' chunk.
138
   * @return {!Array<number>} The 'iXML' chunk bytes.
139
   * @private
140
   */
141
  getiXMLBytes_() {
142
    /** @type {!Array<number>} */
143
    let bytes = [];
144
    if (this.iXML.chunkId) {
145
      bytes = bytes.concat(
146
        packString(this.iXML.chunkId),
147
        pack(this.iXML.chunkSize, this.uInt32),
148
        packString(this.iXML.value));
149
    }
150
    this.enforceByteLen_(bytes);
151
    return bytes;
152
  }
153
154
  /**
155
   * Return the bytes of the 'ds64' chunk.
156
   * @return {!Array<number>} The 'ds64' chunk bytes.
157
   * @private
158
   */
159
  getDs64Bytes_() {
160
    /** @type {!Array<number>} */
161
    let bytes = [];
162
    if (this.ds64.chunkId) {
163
      bytes = bytes.concat(
164
        packString(this.ds64.chunkId),
165
        pack(this.ds64.chunkSize, this.uInt32),
166
        pack(this.ds64.riffSizeHigh, this.uInt32),
167
        pack(this.ds64.riffSizeLow, this.uInt32),
168
        pack(this.ds64.dataSizeHigh, this.uInt32),
169
        pack(this.ds64.dataSizeLow, this.uInt32),
170
        pack(this.ds64.originationTime, this.uInt32),
171
        pack(this.ds64.sampleCountHigh, this.uInt32),
172
        pack(this.ds64.sampleCountLow, this.uInt32));
173
    }
174
    //if (this.ds64.tableLength) {
175
    //  ds64Bytes = ds64Bytes.concat(
176
    //    pack(this.ds64.tableLength, this.uInt32),
177
    //    this.ds64.table);
178
    //}
179
    this.enforceByteLen_(bytes);
180
    return bytes;
181
  }
182
183
  /**
184
   * Return the bytes of the 'cue ' chunk.
185
   * @return {!Array<number>} The 'cue ' chunk bytes.
186
   * @private
187
   */
188
  getCueBytes_() {
189
    /** @type {!Array<number>} */
190
    let bytes = [];
191
    if (this.cue.chunkId) {
192
      /** @type {!Array<number>} */
193
      let cuePointsBytes = this.getCuePointsBytes_();
194
      bytes = bytes.concat(
195
        packString(this.cue.chunkId),
196
        pack(cuePointsBytes.length + 4, this.uInt32),
197
        pack(this.cue.dwCuePoints, this.uInt32),
198
        cuePointsBytes);
199
    }
200
    this.enforceByteLen_(bytes);
201
    return bytes;
202
  }
203
204
  /**
205
   * Return the bytes of the 'cue ' points.
206
   * @return {!Array<number>} The 'cue ' points as an array of bytes.
207
   * @private
208
   */
209
  getCuePointsBytes_() {
210
    /** @type {!Array<number>} */
211
    let points = [];
212
    for (let i=0; i<this.cue.dwCuePoints; i++) {
213
      points = points.concat(
214
        pack(this.cue.points[i].dwName, this.uInt32),
215
        pack(this.cue.points[i].dwPosition, this.uInt32),
216
        packString(this.cue.points[i].fccChunk),
217
        pack(this.cue.points[i].dwChunkStart, this.uInt32),
218
        pack(this.cue.points[i].dwBlockStart, this.uInt32),
219
        pack(this.cue.points[i].dwSampleOffset, this.uInt32));
220
    }
221
    return points;
222
  }
223
224
  /**
225
   * Return the bytes of the 'smpl' chunk.
226
   * @return {!Array<number>} The 'smpl' chunk bytes.
227
   * @private
228
   */
229
  getSmplBytes_() {
230
    /** @type {!Array<number>} */
231
    let bytes = [];
232
    if (this.smpl.chunkId) {
233
      /** @type {!Array<number>} */
234
      let smplLoopsBytes = this.getSmplLoopsBytes_();
235
      bytes = bytes.concat(
236
        packString(this.smpl.chunkId),
237
        pack(smplLoopsBytes.length + 36, this.uInt32),
238
        pack(this.smpl.dwManufacturer, this.uInt32),
239
        pack(this.smpl.dwProduct, this.uInt32),
240
        pack(this.smpl.dwSamplePeriod, this.uInt32),
241
        pack(this.smpl.dwMIDIUnityNote, this.uInt32),
242
        pack(this.smpl.dwMIDIPitchFraction, this.uInt32),
243
        pack(this.smpl.dwSMPTEFormat, this.uInt32),
244
        pack(this.smpl.dwSMPTEOffset, this.uInt32),
245
        pack(this.smpl.dwNumSampleLoops, this.uInt32),
246
        pack(this.smpl.dwSamplerData, this.uInt32),
247
        smplLoopsBytes);
248
    }
249
    this.enforceByteLen_(bytes);
250
    return bytes;
251
  }
252
253
  /**
254
   * Return the bytes of the 'smpl' loops.
255
   * @return {!Array<number>} The 'smpl' loops as an array of bytes.
256
   * @private
257
   */
258
  getSmplLoopsBytes_() {
259
    /** @type {!Array<number>} */
260
    let loops = [];
261
    for (let i=0; i<this.smpl.dwNumSampleLoops; i++) {
262
      loops = loops.concat(
263
        pack(this.smpl.loops[i].dwName, this.uInt32),
264
        pack(this.smpl.loops[i].dwType, this.uInt32),
265
        pack(this.smpl.loops[i].dwStart, this.uInt32),
266
        pack(this.smpl.loops[i].dwEnd, this.uInt32),
267
        pack(this.smpl.loops[i].dwFraction, this.uInt32),
268
        pack(this.smpl.loops[i].dwPlayCount, this.uInt32));
269
    }
270
    return loops;
271
  }
272
273
  /**
274
   * Return the bytes of the 'fact' chunk.
275
   * @return {!Array<number>} The 'fact' chunk bytes.
276
   * @private
277
   */
278
  getFactBytes_() {
279
    /** @type {!Array<number>} */
280
    let bytes = [];
281
    if (this.fact.chunkId) {
282
      bytes = bytes.concat(
283
        packString(this.fact.chunkId),
284
        pack(this.fact.chunkSize, this.uInt32),
285
        pack(this.fact.dwSampleLength, this.uInt32));
286
    }
287
    this.enforceByteLen_(bytes);
288
    return bytes;
289
  }
290
291
  /**
292
   * Return the bytes of the 'fmt ' chunk.
293
   * @return {!Array<number>} The 'fmt' chunk bytes.
294
   * @throws {Error} if no 'fmt ' chunk is present.
295
   * @private
296
   */
297
  getFmtBytes_() {
298
    /** @type {!Array<number>} */
299
    let fmtBytes = [];
300
    if (this.fmt.chunkId) {
301
      let bytes  = fmtBytes.concat(
302
        packString(this.fmt.chunkId),
303
        pack(this.fmt.chunkSize, this.uInt32),
304
        pack(this.fmt.audioFormat, this.uInt16),
305
        pack(this.fmt.numChannels, this.uInt16),
306
        pack(this.fmt.sampleRate, this.uInt32),
307
        pack(this.fmt.byteRate, this.uInt32),
308
        pack(this.fmt.blockAlign, this.uInt16),
309
        pack(this.fmt.bitsPerSample, this.uInt16),
310
        this.getFmtExtensionBytes_());
311
      this.enforceByteLen_(bytes);
312
      return bytes;
313
    }
314
    throw Error('Could not find the "fmt " chunk');
315
  }
316
317
  /**
318
   * Return the bytes of the fmt extension fields.
319
   * @return {!Array<number>} The fmt extension bytes.
320
   * @private
321
   */
322
  getFmtExtensionBytes_() {
323
    /** @type {!Array<number>} */
324
    let extension = [];
325
    if (this.fmt.chunkSize > 16) {
326
      extension = extension.concat(
327
        pack(this.fmt.cbSize, this.uInt16));
328
    }
329
    if (this.fmt.chunkSize > 18) {
330
      extension = extension.concat(
331
        pack(this.fmt.validBitsPerSample, this.uInt16));
332
    }
333
    if (this.fmt.chunkSize > 20) {
334
      extension = extension.concat(
335
        pack(this.fmt.dwChannelMask, this.uInt32));
336
    }
337
    if (this.fmt.chunkSize > 24) {
338
      extension = extension.concat(
339
        pack(this.fmt.subformat[0], this.uInt32),
340
        pack(this.fmt.subformat[1], this.uInt32),
341
        pack(this.fmt.subformat[2], this.uInt32),
342
        pack(this.fmt.subformat[3], this.uInt32));
343
    }
344
    return extension;
345
  }
346
347
  /**
348
   * Return the bytes of the 'LIST' chunk.
349
   * @return {!Array<number>} The 'LIST' chunk bytes.
350
   * @private
351
   */
352
  getLISTBytes_() {
353
    /** @type {!Array<number>} */
354
    let bytes = [];
355
    for (let i=0; i<this.LIST.length; i++) {
356
      /** @type {!Array<number>} */
357
      let subChunksBytes = this.getLISTSubChunksBytes_(
358
          this.LIST[i].subChunks, this.LIST[i].format);
359
      bytes = bytes.concat(
360
        packString(this.LIST[i].chunkId),
361
        pack(subChunksBytes.length + 4, this.uInt32),
362
        packString(this.LIST[i].format),
363
        subChunksBytes);
364
    }
365
    this.enforceByteLen_(bytes);
366
    return bytes;
367
  }
368
369
  /**
370
   * Return the bytes of the sub chunks of a 'LIST' chunk.
371
   * @param {!Array<!Object>} subChunks The 'LIST' sub chunks.
372
   * @param {string} format The format of the 'LIST' chunk.
373
   *    Currently supported values are 'adtl' or 'INFO'.
374
   * @return {!Array<number>} The sub chunk bytes.
375
   * @private
376
   */
377
  getLISTSubChunksBytes_(subChunks, format) {
378
    /** @type {!Array<number>} */
379
    let bytes = [];
380
    for (let i=0; i<subChunks.length; i++) {
381
      if (format == 'INFO') {
382
        bytes = bytes.concat(
383
          packString(subChunks[i].chunkId),
384
          pack(subChunks[i].value.length + 1, this.uInt32),
385
          writeString(
386
            subChunks[i].value, subChunks[i].value.length));
387
        bytes.push(0);
388
      } else if (format == 'adtl') {
389
        if (['labl', 'note'].indexOf(subChunks[i].chunkId) > -1) {
390
          bytes = bytes.concat(
391
            packString(subChunks[i].chunkId),
392
            pack(
393
              subChunks[i].value.length + 4 + 1, this.uInt32),
394
            pack(subChunks[i].dwName, this.uInt32),
395
            writeString(
396
              subChunks[i].value,
397
              subChunks[i].value.length));
398
          bytes.push(0);
399
        } else if (subChunks[i].chunkId == 'ltxt') {
400
          bytes = bytes.concat(
401
            this.getLtxtChunkBytes_(subChunks[i]));
402
        }
403
      }
404
      this.enforceByteLen_(bytes);
405
    }
406
    return bytes;
407
  }
408
409
  /**
410
   * Return the bytes of the '_PMX' chunk.
411
   * @return {!Array<number>} The '_PMX' chunk bytes.
412
   * @private
413
   */
414
  get_PMXBytes_() {
415
    /** @type {!Array<number>} */
416
    let bytes = [];
417
    if (this._PMX.chunkId) {
418
      bytes = bytes.concat(
419
        packString(this._PMX.chunkId),
420
        pack(this._PMX.chunkSize, this.uInt32),
421
        packString(this._PMX.value));
422
    }
423
    this.enforceByteLen_(bytes);
424
    return bytes;
425
  }
426
427
  /**
428
   * Return the bytes of a 'ltxt' chunk.
429
   * @param {!Object} ltxt the 'ltxt' chunk.
430
   * @private
431
   */
432
  getLtxtChunkBytes_(ltxt) {
433
    return [].concat(
434
      packString(ltxt.chunkId),
435
      pack(ltxt.value.length + 20, this.uInt32),
436
      pack(ltxt.dwName, this.uInt32),
437
      pack(ltxt.dwSampleLength, this.uInt32),
438
      pack(ltxt.dwPurposeID, this.uInt32),
439
      pack(ltxt.dwCountry, this.uInt16),
440
      pack(ltxt.dwLanguage, this.uInt16),
441
      pack(ltxt.dwDialect, this.uInt16),
442
      pack(ltxt.dwCodePage, this.uInt16),
443
      writeString(ltxt.value, ltxt.value.length));
444
  }
445
446
  /**
447
   * Return the bytes of the 'junk' chunk.
448
   * @private
449
   */
450
  getJunkBytes_() {
451
    /** @type {!Array<number>} */
452
    let bytes = [];
453
    if (this.junk.chunkId) {
454
      return bytes.concat(
455
        packString(this.junk.chunkId),
456
        pack(this.junk.chunkData.length, this.uInt32),
457
        this.junk.chunkData);
458
    }
459
    this.enforceByteLen_(bytes);
460
    return bytes;
461
  }
462
463
  /**
464
   * Push a null byte into a byte array if
465
   * the byte count is odd.
466
   * @param {!Array<number>} bytes The byte array.
467
   * @private
468
   */
469
  enforceByteLen_(bytes) {
470
    if (bytes.length % 2) {
471
      bytes.push(0);
472
    }
473
  }
474
}
475